﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Microsoft.SharePoint.Client;
using MSDN.Samples.ClaimsAuth;
using SharePoint.Security.ContentScanner.Engine.Helpers;
using SharePoint.Security.DocHelpers.ExportFormats;
using File = System.IO.File;

namespace SharePoint.Security.ContentScanner.Engine.Analyzers
{
    public class Scanner
    {
        public List<ErrorInfo> Errors { get; set; }
        public int sitesSuccessfullyScanned = 0;
        public int previousSelectedSites = 0;
        public int previousScannedSites = 0;
        public int totalSitesSelectedForScan = 0;

        public Scanner()
        {

            Errors = new List<ErrorInfo>();
            ScanResultsForSite = new List<ScanResult>();
            ErrorListForSite = new List<ErrorInfo>();
        }

        private List<ScanResult> ScanResultsForSite = null;
        private List<ErrorInfo> ErrorListForSite = null;

        public void Scan(string site, List<Pattern> patterns, List<SelectedFileExtension> fileExtns, string type, string scanFileName)
        {
            int libraryCount = 0;

            using (var clientContext = Helper.GetClientContext(site.Trim(), type))
            {
                var libraries = GetLibraries(clientContext);

                var filesToScan = new ConcurrentBag<SPFileToScan>();

                libraries = libraries.ToList();
                libraryCount = libraries.Count();
                if (libraryCount != 0)
                {
                    ParallelProcessing(site, patterns, fileExtns, type, libraries, filesToScan);
                }
            }

            //Write scan results instantly to a text file
            WriteScanResultToFile(scanFileName);

            if (libraryCount > 0)
            {
                // writing successfully scanned urls to the file.
                File.AppendAllText(@"ScanFiles\SuccessfullyScannedSitesList.txt", site.Trim() + Environment.NewLine);

                sitesSuccessfullyScanned++;

                // writing successfully scanned urls count to file.
                if (File.Exists(@"ScanFiles\SitesScannedSummary.txt"))
                {
                    File.Delete(@"ScanFiles\SitesScannedSummary.txt");
                } 
                File.AppendAllText(@"ScanFiles\SitesScannedSummary.txt", (sitesSuccessfullyScanned + previousScannedSites).ToString() + "," + "No. of sites scanned" + Environment.NewLine);
                File.AppendAllText(@"ScanFiles\SitesScannedSummary.txt", (totalSitesSelectedForScan).ToString()  + "," + "No. of sites selected for scan" + Environment.NewLine);

                // writing successfully found pattern match count to file.
                if (File.Exists(@"ScanFiles\PatternSummary.txt"))
                {
                    File.Delete(@"ScanFiles\PatternSummary.txt");
                }
                foreach (var patternValue in Helper.scanPatternSummary)
                {
                    File.AppendAllText(@"ScanFiles\PatternSummary.txt", patternValue.Value.ToString() + "," + patternValue.Key.ToString() + Environment.NewLine);
                }                
            }         
        }

        private void AppendToSummaryData()
        {
            var logger = Logger.Log;
            var distinctPatterns = ScanResultsForSite
                        .GroupBy(a => a.Pattern)
                        .Select(g => g.First().Pattern)
                        .ToList();

            foreach (var pattern in distinctPatterns)
            {
                int currentCount = ScanResultsForSite.Count(c => c.Pattern.Equals(pattern));                

                if (Helper.scanPatternSummary.Any(x => x.Key == pattern))
                {
                    foreach (SummaryInfo info in Helper.scanPatternSummary)
                    {
                        if (info.Key == pattern)
                        {
                            int patternCount = 0;
                            if (int.TryParse(info.Value, out patternCount))
                            {
                                patternCount = patternCount + currentCount;
                                info.Value = patternCount.ToString();
                            }
                            else
                            {
                                logger.Error(
                                    string.Format(
                                        "Error while writting to Pattern Summary data: Failed to convert {0} value to integer.",
                                        info.Key));
                            }
                        }
                    }
                }
                else
                {
                    Helper.scanPatternSummary.Add(new SummaryInfo() { Key = pattern, Value = currentCount.ToString() });
                }                              
            }            
        }

        private void SequentialProcessing(string site, List<Pattern> patterns, List<SelectedFileExtension> fileExtns, string type, List<string> libraries, ConcurrentBag<SPFileToScan> filesToScan)
        {
            foreach (var currentLibrary in libraries)
            {
                try
                {
                    //GetFiles(site.Trim(), currentLibrary, fileExtns, filesToScan, type);
                }
                catch (Exception ex)
                {
                    Errors.Add(new ErrorInfo(
                                   string.Format("Failed to process library {0}. Error: {1}", currentLibrary, ex.Message)));

                }

            }

            foreach (var currentFile in filesToScan.ToList())
            {
                try
                {
                    FetchAndSearchFile(currentFile, patterns, type, site.Trim());
                }
                catch (Exception ex)
                {
                    Errors.Add(new ErrorInfo(
                                   string.Format(
                        "Failed to scan file {0}. Error: {1}", currentFile, ex.Message)));
                }
            }
        }

        private void ParallelProcessing(string site, List<Pattern> patterns, List<SelectedFileExtension> fileExtns, string type, List<string> libraries, ConcurrentBag<SPFileToScan> filesToScan)
        {
            Parallel.ForEach(libraries, currentLibrary =>
            {
                try
                {
                    GetFiles(site.Trim(), currentLibrary, fileExtns, filesToScan, type, site, patterns);
                }
                catch (Exception ex)
                {
                    ErrorInfo errorInfo = new ErrorInfo(
                                   string.Format("Failed to process library {0}. Error: {1}", currentLibrary, ex.Message));
                    ErrorListForSite.Add(errorInfo);
                    Errors.Add(errorInfo);
                }

            });

            
            //foreach (string currentLibrary in libraries)
            //{
            //    try
            //    {
            //        GetFiles(site.Trim(), currentLibrary, fileExtns, filesToScan, type, site, patterns);
            //    }
            //    catch (Exception ex)
            //    {
            //        ErrorInfo errorInfo = new ErrorInfo(
            //                        string.Format("Failed to process library {0}. Error: {1}", currentLibrary, ex.Message));
            //        ErrorListForSite.Add(errorInfo);
            //        Errors.Add(errorInfo);
            //    }
            //}

            //Parallel.ForEach(filesToScan.ToList(), currentFile =>
            //{
            //    try
            //    {
            //        FetchAndSearchFile(currentFile, patterns, type, site.Trim());
            //    }
            //    catch (Exception ex)
            //    {
            //        Errors.Add(new ErrorInfo(
            //                       string.Format(
            //            "Failed to scan file {0}. Error: {1}", currentFile, ex.Message)));
            //    }

            //});
        }

        private void GetFiles(string url, string currentLibrary, List<SelectedFileExtension> fileExtns, ConcurrentBag<SPFileToScan> filesToScan, string type, string site, List<Pattern> patterns)
        {
            using (var clientContext = Helper.GetClientContext(url.Trim(), type))
            {
                List list = clientContext.Web.Lists.GetByTitle(currentLibrary);
                clientContext.Load(list);
                clientContext.ExecuteQuery();
                FileCollection files = list.RootFolder.Files;
                clientContext.Load(files);
                clientContext.ExecuteQuery();
               
                Parallel.ForEach(files, file =>
                {
                    if (Helper.CanScanDocument(file.Name, fileExtns))
                    {
                        string docUrl = file.GetFileUrl();

                        SPFileToScan fileToScan = new SPFileToScan
                                            {
                                                FileObj = file,
                                                URL = docUrl
                                            };

                        FetchAndSearchFile(fileToScan, patterns, type, site.Trim());

                    }
                });
            }
        }

        public List<string> GetLibraries(ClientContext ctx)
        {
            var libraries = new List<string>();
            try
            {

                Web web = ctx.Site.RootWeb;


                var query = from l in web.Lists
                    .Include(l => l.Title)
                    where l.Hidden == false
                    select l;

                var lists = ctx.LoadQuery(query);

                ctx.ExecuteQuery();

                foreach (var item in lists)
                {
                    libraries.Add(item.Title);
                }

                return libraries;
            }
            catch (WebException we)
            {
                //This exception mostly occurs when the user selects following - Process Subsites(Unchecked), Provide Credentials(Checked) and then scan
                ErrorInfo errorInfo = new ErrorInfo(
                    string.Format("Failed to get libraries for {0}. {1}. Please check credentials provided and retry.",
                        we.Response.ResponseUri.ToString(), we.Message));
                ErrorListForSite.Add(errorInfo);
                Errors.Add(errorInfo);

                return libraries;
            }
            catch (Exception ex)
            {
                ErrorInfo errorInfo = new ErrorInfo(ex.Message);
                ErrorListForSite.Add(errorInfo);
                Errors.Add(errorInfo);

                return libraries;
            }            
        }

        private void FetchAndSearchFile(SPFileToScan spFileToScan, List<Pattern> patterns, string type, string siteUrl)
        {
            var logger = Logger.Log;
            if (patterns == null || patterns.Count == 0)
            {
                throw new ArgumentException("Search string is either null or empty");
            }

            if (string.IsNullOrEmpty(spFileToScan.URL))
            {
                throw new ArgumentException("URL is either null or empty");
            }

            string strFileName = Path.GetFileName(spFileToScan.URL);
            if (string.IsNullOrEmpty(strFileName))
            {
                throw new ArgumentException(string.Format("Filename is not found using url [{0}]", spFileToScan.URL));
            }

            var request = (HttpWebRequest)WebRequest.Create(spFileToScan.URL);
            if (ClaimClientContext.AuthCookie != null)
            {
                request.CookieContainer = new CookieContainer();
                request.CookieContainer.Add(ClaimClientContext.AuthCookie);
            }

            request.Credentials = CredentialCache.DefaultCredentials;
            request.Timeout = 1000000000;
            request.AllowWriteStreamBuffering = false;

            logger.Info(string.Format("Started fetching the File {0} from the Sharepoint site at {1}", spFileToScan.URL, DateTime.Now.ToString()));

            var response = (HttpWebResponse)request.GetResponse();
            Stream responsStream = response.GetResponseStream();

            logger.Info(string.Format("Completed fetching the File {0} from the Sharepoint site at {1}", spFileToScan.URL, DateTime.Now.ToString()));

            //Write to disk
            logger.Info(string.Format("Started writing the File {0} to the disk at {1}", spFileToScan.URL, DateTime.Now.ToString()));
            var tempFilePath = string.Format("{0}_{1}_{2}", Path.GetTempPath(), Guid.NewGuid(), strFileName);
            var fs = new FileStream(tempFilePath, FileMode.Create);

            var read = new byte[256];
            if (responsStream != null)
            {
                int count = responsStream.Read(read, 0, read.Length);

                while (count > 0)
                {
                    fs.Write(read, 0, count);
                    count = responsStream.Read(read, 0, read.Length);
                }
            }

            fs.Close();
            if (responsStream != null) responsStream.Close();
            response.Close();
            logger.Info(string.Format("Completed writing the File {0} to the disk at {1}", spFileToScan.URL, DateTime.Now.ToString()));
            string ext = Path.GetExtension(strFileName);

            try
            {
                logger.Info(string.Format("Started scanning the File {0} at {1}", spFileToScan.URL, DateTime.Now.ToString()));
                IDocumentScanner docScanner = DocumentScannerFactory.Create(ext);

                var scanResults = docScanner.Scan(tempFilePath, patterns, spFileToScan.URL);
                logger.Info(string.Format("Completed scanning the File {0} at {1}", spFileToScan.URL, DateTime.Now.ToString()));

                if (scanResults != null && scanResults.Count > 0)
                {
                    using (var clientContext = Helper.GetClientContext(siteUrl.Trim(), type))
                    {
                        var perm = Helper.GetPermissions(spFileToScan.FileObj, clientContext);

                        for (int i = 0; i < scanResults.Count; i++)
                        {
                            scanResults[i].Permissions = perm;
                        }
                    }

                    ScanResultsForSite.AddRange(scanResults);
                    //ScanResults.AddRange(scanResults);
                }
            }
            finally
            {
                logger.Info(string.Format("Started deleting the File {0} at {1}", tempFilePath, DateTime.Now.ToString()));
                File.Delete(tempFilePath);
                logger.Info(string.Format("Completed deleting the  File {0} at {1}", tempFilePath, DateTime.Now.ToString()));
            }
        }

        private void WriteScanResultToFile(string scanFileName)
        {
            try
            {
                //Write to excel using helper class
                ExcelHelper excelHelp = new ExcelHelper();
                excelHelp.AddResults(scanFileName, ScanResultsForSite);
                excelHelp.AddErrors(scanFileName, ErrorListForSite);
                //excelHelp.AddSummary(scanFileName, ScanResults);              
                AppendToSummaryData();

                // writing scanned urls error info to file.
                foreach (ErrorInfo eInfo in ErrorListForSite)
                {
                    File.AppendAllText(@"ScanFiles\ErrorSummary.txt", eInfo.Message + Environment.NewLine);
                }

                //Delete all the scan results for a given site
                ScanResultsForSite.Clear();
                ErrorListForSite.Clear();
                                              
            }
            catch (Exception e)
            {
                ScanResultsForSite.Clear();
                ErrorListForSite.Clear();
                //Log error to say the data for the site could not be saved
                Errors.Add(new ErrorInfo(
                                   string.Format("Error while writing to excel file: {0}", e.Message)));
            }
        }
    }

    public class SPFileToScan
    {
        public string URL { get; set; }

        public long FileSize { get; set; }

        public Microsoft.SharePoint.Client.File FileObj { get; set; }
    }
}
